import 'dart:async';
import 'dart:collection';
import 'dart:math';

import 'package:async/async.dart';
import 'package:stack_trace/stack_trace.dart';

import '../common/test_page.dart';

final someStack = StackTrace.current;

Result<int> res(int n) => Result<int>.value(n);

Result err(int n) => ErrorResult('$n', someStack);

/// Helper function creating an iterable of futures.
Iterable<Future<int>> futures(int count,
    {bool Function(int index)? throwWhen}) sync* {
  for (var i = 0; i < count; i++) {
    if (throwWhen != null && throwWhen(i)) {
      yield Future<int>.error('$i', someStack);
    } else {
      yield Future<int>.value(i);
    }
  }
}

Result<T> res1<T>(T n) => Result<T>.value(n);
Result<T> err1<T>(int n) => ErrorResult('$n', someStack);

/// Helper function creating an iterable of results.
Iterable<Result<int>> results(int count,
    {bool Function(int index)? throwWhen}) sync* {
  for (var i = 0; i < count; i++) {
    if (throwWhen != null && throwWhen(i)) {
      yield err1(i);
    } else {
      yield res1(i);
    }
  }
}

class ResultTestPage extends TestPage {
  ResultTestPage(super.title) {
    group('Result.captureAll()', () {
      test('await Result.captureAll<int>(futures(0))', () async {
        var all = await Result.captureAll<int>(futures(0));
        expect(all);
      });

      test('await Result.captureAll<int>(futures(1))', () async {
        var all = await Result.captureAll<int>(futures(1));
        expect(all);
      });

      test('await Result.captureAll<int>(futures(3))', () async {
        var all = await Result.captureAll<int>(futures(3));
        expect(all);
      });

      test('await Result.captureAll<int>(futures(1, throwWhen: (_) => true))', () async {
        var all = await Result.captureAll<int>(futures(1, throwWhen: (_) => true));
        expect(all);
      });

      test('await Result.captureAll<int>(futures(3, throwWhen: (_) => true))', () async {
        var all = await Result.captureAll<int>(futures(3, throwWhen: (_) => true));
        expect(all);
      });

      test('await Result.captureAll<int>(futures(4, throwWhen: (x) => x.isOdd))', () async {
        var all = await Result.captureAll<int>(futures(4, throwWhen: (x) => x.isOdd));
        expect(all);
      });

      test('完成置换1-2-3', () async {
        var cs = List.generate(3, (_) => Completer<int>());
        var all = Result.captureAll<int>(cs.map((c) => c.future));
        expect(all);
        await _microTask();
        cs[0].complete(1);
        await _microTask();
        cs[1].complete(2);
        await _microTask();
        cs[2].completeError('3', someStack);
      });

      test('完成置换1-3-2', () async {
        var cs = List.generate(3, (_) => Completer<int>());
        var all = Result.captureAll<int>(cs.map((c) => c.future));
        expect(all);
        await _microTask();
        cs[0].complete(1);
        await _microTask();
        cs[2].completeError('3', someStack);
        await _microTask();
        cs[1].complete(2);
      });

      test('完成排列2-1-3', () async {
        var cs = List.generate(3, (_) => Completer<int>());
        var all = Result.captureAll<int>(cs.map((c) => c.future));
        expect(all);
        await _microTask();
        cs[1].complete(2);
        await _microTask();
        cs[0].complete(1);
        await _microTask();
        cs[2].completeError('3', someStack);
      });

      test('完成排列2-3-1', () async {
        var cs = List.generate(3, (_) => Completer<int>());
        var all = Result.captureAll<int>(cs.map((c) => c.future));
        expect(all);
        await _microTask();
        cs[1].complete(2);
        await _microTask();
        cs[2].completeError('3', someStack);
        await _microTask();
        cs[0].complete(1);
      });

      test('完成置换3-1-2', () async {
        var cs = List.generate(3, (_) => Completer<int>());
        var all = Result.captureAll<int>(cs.map((c) => c.future));
        expect(all);
        await _microTask();
        cs[2].completeError('3', someStack);
        await _microTask();
        cs[0].complete(1);
        await _microTask();
        cs[1].complete(2);
      });

      test('完成排列3-2-1', () async {
        var cs = List.generate(3, (_) => Completer<int>());
        var all = Result.captureAll<int>(cs.map((c) => c.future));
        expect(all);
        await _microTask();
        cs[2].completeError('3', someStack);
        await _microTask();
        cs[1].complete(2);
        await _microTask();
        cs[0].complete(1);
      });

      var seed = Random().nextInt(0x100000000);
      var n = 25;
      test('randomized #$n seed:${seed.toRadixString(16)}', () async {
        var cs = List.generate(n, (_) => Completer<int>());
        var all = Result.captureAll<int>(cs.map((c) => c.future));
        var rnd = Random(seed);
        var throwFlags = rnd.nextInt(1 << n);
        bool throws(int index) => (throwFlags & (1 << index)) != 0;
        var expected = List.generate(n, (x) => throws(x) ? err(x) : res(x));

        expect(all);

        var completeFunctions = List<Function()>.generate(n, (i) {
          var c = cs[i];
          return () =>
          throws(i) ? c.completeError('$i', someStack) : c.complete(i);
        });
        completeFunctions.shuffle(rnd);
        for (var i = 0; i < n; i++) {
          await _microTask();
          completeFunctions[i]();
        }
      });

      test('await Result.captureAll<int>(<int>[1])', () async {
        var all = await Result.captureAll<int>(<int>[1]);
        expect(all);
      });
      test('await Result.captureAll<int>(<int>[1, 2, 3])', () async {
        var all = await Result.captureAll<int>(<int>[1, 2, 3]);
        expect(all);
      });

      test('await Result.captureAll<int>(<FutureOr<int>>[1,Future<int>(() => 2),3,Future<int>.value(4),])', () async {
        var all = await Result.captureAll<int>(<FutureOr<int>>[
          1,
          Future<int>(() => 2),
          3,
          Future<int>.value(4),
        ]);
        expect(all);
      });
      test('await Result.captureAll<int>(<FutureOr<int>>[1,Future<int>(() => 2),3,Future<int>(() async => await Future.error("4", someStack)),Future<int>.value(5)])', () async {
        var all = await Result.captureAll<int>(<FutureOr<int>>[
          1,
          Future<int>(() => 2),
          3,
          Future<int>(() async => await Future.error('4', someStack)),
          Future<int>.value(5)
        ]);
        expect(all);
      });
    });

    group('Result.flattenAll()', () {
      void expectAll<T>(Result<T> result, Result<T> expectation) {
        if (expectation.isError) {
          expect(result);
        } else {
          expect(result.isValue);
          expect(result.asValue!);
        }
      }

      test('Result.flattenAll<int>(results(0)), res([])', () {
        expectAll(Result.flattenAll<int>(results(0)), res1([]));
      });
      test('Result.flattenAll<int>(results(1)), res([0])', () {
        expectAll(Result.flattenAll<int>(results(1)), res1([0]));
      });
      test('Result.flattenAll<int>(results(1, throwWhen: (_) => true)), err(0)', () {
        expectAll(Result.flattenAll<int>(results(1, throwWhen: (_) => true)), err(0));
      });
      test('Result.flattenAll<int>(results(5)), res1([0, 1, 2, 3, 4])', () {
        expectAll(Result.flattenAll<int>(results(5)), res1([0, 1, 2, 3, 4]));
      });
      test('Result.flattenAll<int>(results(5, throwWhen: (x) => x.isOdd)),err(1)', () {
        expectAll(Result.flattenAll<int>(results(5, throwWhen: (x) => x.isOdd)),
            err(1)); // First error is result.
      });
      test('Result.flattenAll<int>(results(5, throwWhen: (x) => x == 4)), err(4)', () {
        expectAll(Result.flattenAll<int>(results(5, throwWhen: (x) => x == 4)), err(4));
      });
    });

    group('ResultFuture()', () {
      test('ResultFuture(Completer().future)', () {
        Completer completer = Completer();
        ResultFuture future = ResultFuture(completer.future);
        expect(future.result);
      });

      test('Completer().complete(12)成功完成后，结果就是未来的值', () {
        Completer completer = Completer();
        ResultFuture future = ResultFuture(completer.future);
        completer.complete(12);

        expect(future.then((_) => future.result!.asValue!.value));
      });

      test("after an error completion, result is the future's error", () {
        Completer completer = Completer();
        ResultFuture future = ResultFuture(completer.future);
        var trace = Trace.current();
        completer.completeError('error', trace);

        return future.catchError((_) {}).then((_) {
          var error = future.result!.asError!;
          expect(error.error);
          expect(error.stackTrace);
        });
      });
    });

    group('Result', () {
      var stack = Trace.current();

      test('Result<int>.value(42)', () {
        var result = Result<int>.value(42);
        expect(result.isValue);
        expect(result.isError);
        ValueResult value = result.asValue!;
        expect(value.value);
      });

      test('ValueResult<int>(42)', () {
        Result<int> result = ValueResult<int>(42);
        expect(result.isValue);
        expect(result.isError);
        var value = result.asValue!;
        expect(value.value);
      });

      test('Result<bool>.error("BAD", stack)', () {
        var result = Result<bool>.error('BAD', stack);
        expect(result.isValue);
        expect(result.isError);
        var error = result.asError!;
        expect(error.error);
        expect(error.stackTrace);
      });

      test('ErrorResult("BAD", stack)', () {
        var result = ErrorResult('BAD', stack);
        expect(result.isValue);
        expect(result.isError);
        var error = result.asError;
        expect(error.error);
        expect(error.stackTrace);
      });

      test('Result<bool>.error("BAD")', () {
        var result = Result<bool>.error('BAD');
        expect(result.isValue);
        expect(result.isError);
        var error = result.asError!;
        expect(error.error);
        expect(error.stackTrace);
      });

      test('ValueResult<int>(42).complete(Completer<int>())', () {
        Result<int> result = ValueResult<int>(42);
        var c = Completer<int>();
        c.future.then((int v) {
          expect(v);
        }, onError: (e, s) {
          fail('Unexpected error');
        });
        result.complete(c);
      });

      test('ErrorResult("BAD", stack).complete(Completer<bool>())', () {
        Result<bool> result = ErrorResult('BAD', stack);
        var c = Completer<bool>();
        c.future.then((bool v) {
          fail('Unexpected value $v');
        }).then<void>((_) {}, onError: (e, s) {
          expect(e);
          expect(s);
        });
        result.complete(c);
      });

      test('ValueResult<int>(42).addTo(TestSink(onData:))', () {
        var result = ValueResult<int>(42);
        EventSink<int> sink = TestSink(onData: (v) {
          expect(v);
        });
        result.addTo(sink);
      });

      test('ErrorResult("BAD", stack).addTo(TestSink(onError:))', () {
        Result<bool> result = ErrorResult('BAD', stack);
        EventSink<bool> sink = TestSink(onError: (e, s) {
          expect(e);
          expect(s);
        });
        result.addTo(sink);
      });

      test('ValueResult<int>(42).asFuture.then', () {
        Result<int> result = ValueResult<int>(42);
        result.asFuture.then((int v) {
          expect(v);
        }, onError: (e, s) {
          fail('Unexpected error');
        });
      });

      test('ErrorResult("BAD", stack).asFuture.then', () {
        Result<bool> result = ErrorResult('BAD', stack);
        result.asFuture.then((bool v) {
          fail('Unexpected value $v');
        }).then<void>((_) {}, onError: (e, s) {
          expect(e);
          expect(s);
        });
      });

      test('Future<int>.value(42).capture(value).then', () {
        var value = Future<int>.value(42);
        Result.capture(value).then((Result result) {
          expect(result.isValue);
          expect(result.isError);
          var value = result.asValue!;
          expect(value.value);
        }, onError: (e, s) {
          fail('Unexpected error: $e');
        });
      });

      test('Future<bool>.error("BAD", stack).capture(value).then', () {
        var value = Future<bool>.error('BAD', stack);
        Result.capture(value).then((Result result) {
          expect(result.isValue);
          expect(result.isError);
          var error = result.asError!;
          expect(error.error);
          expect(error.stackTrace);
        }, onError: (e, s) {
          fail('Unexpected error: $e');
        });
      });

      test('Future<Result<int>>.value(Result<int>.value(42)).release(future).then', () {
        var future = Future<Result<int>>.value(Result<int>.value(42));
        Result.release(future).then((v) {
          expect(v);
        }, onError: (e, s) {
          fail('Unexpected error: $e');
        });
      });

      test('Future<Result<bool>>.value(Result<bool>.error("BAD", stack)).release(future).then', () {
        var future = Future<Result<bool>>.value(Result<bool>.error('BAD', stack));
        Result.release(future).then((v) {
          fail('Unexpected value: $v');
        }).then<void>((_) {}, onError: (e, s) {
          expect(e);
          expect(s);
        });
      });

      test('Future<Result<bool>>.error("BAD", stack).release(future).then', () {
        var future = Future<Result<bool>>.error('BAD', stack);
        Result.release(future).then((v) {
          fail('Unexpected value: $v');
        }).then<void>((_) {}, onError: (e, s) {
          expect(e);
          expect(s);
        });
      });

      test('Result.captureStream(StreamController<int>().stream)', () {
        var c = StreamController<int>();
        var stream = Result.captureStream(c.stream);
        var expectedList = Queue.of(
            [Result.value(42), Result.error('BAD', stack), Result.value(37)]);
        void listener(Result actual) {
          expect(expectedList.isEmpty);
          expectResult(actual, expectedList.removeFirst());
        }

        stream.listen(listener, onDone: () {}, cancelOnError: true);
        c.add(42);
        c.addError('BAD', stack);
        c.add(37);
        c.close();
      });

      test('Result.releaseStream(StreamController<Result<int>>().stream)', () {
        var c = StreamController<Result<int>>();
        var stream = Result.releaseStream(c.stream);
        var events = [
          Result<int>.value(42),
          Result<int>.error('BAD', stack),
          Result<int>.value(37)
        ];

        var expectedList = Queue.of(events)..add(Result.error('BAD2', stack));

        void dataListener(int v) {
          expect(expectedList.isEmpty);
          Result expected = expectedList.removeFirst();
          expect(expected.isValue);
          expect(v);
        }

        void errorListener(error, StackTrace stackTrace) {
          expect(expectedList.isEmpty);
          Result expected = expectedList.removeFirst();
          expect(expected.isError);
          expect(error);
          expect(stackTrace);
        }

        stream.listen(dataListener,
            onError: errorListener,
            onDone: () {});
        for (var result in events) {
          c.add(result);
        }
        c.addError('BAD2', stack);
        c.close();
      });

      test('Result.releaseStream(StreamController<Result<int>>().stream).listen()', () {
        var c = StreamController<Result<int>>();
        var stream = Result.releaseStream(c.stream);
        stream.listen((v) {
          expect(v);
        }, onError: (e, s) {
          expect(e);
          expect(s);
        }, onDone: () {
          fail('Unexpected done event');
        }, cancelOnError: true);
        c.add(Result.value(42));
        c.add(Result.error('BAD', stack));
        c.add(Result.value(37));
        c.close();
      });

      test('Result.flatten(Result<Result<int>>.error("BAD", stack))压平误差1', () {
        var error = Result<int>.error('BAD', stack);
        var flattened = Result.flatten(Result<Result<int>>.error('BAD', stack));
        expectResult(flattened, error);
      });

      test('压平误差2', () {
        var error = Result<int>.error('BAD', stack);
        var result = Result<Result<int>>.value(error);
        var flattened = Result.flatten(result);
        expectResult(flattened, error);
      });

      test('Result.flatten(Result<Result<int>>.value(Result<int>.value(42)))压平值', () {
        var result = Result<Result<int>>.value(Result<int>.value(42));
        expectResult(Result.flatten(result), Result<int>.value(42));
      });

      test('ErrorResult("error", stack).handle((error){})处理一元的', () {
        var result = ErrorResult('error', stack);
        var called = false;
        result.handle((error) {
          called = true;
          expect(error);
        });
        expect(called);
      });

      test('ErrorResult("error", stack).handle处理二进制', () {
        var result = ErrorResult('error', stack);
        var called = false;
        result.handle((error, stackTrace) {
          called = true;
          expect(error);
          expect(stackTrace);
        });
        expect(called);
      });

      test('ErrorResult("error", stack).handle处理一元和二进制', () {
        var result = ErrorResult('error', stack);
        var called = false;
        result.handle((error, [stackTrace]) {
          called = true;
          expect(error);
          expect(stackTrace);
        });
        expect(called);
      });

      test('ErrorResult("error", stack).handle既不处理一元也不处理二进制', () {
        var result = ErrorResult('error', stack);
        expect(() => result.handle(() => fail('unreachable')));
        expect(() => result.handle((a, b, c) => fail('unreachable')));
        expect(() => result.handle((a, b, {c}) => fail('unreachable')));
        expect(() => result.handle((a, {b}) => fail('unreachable')));
        expect(() => result.handle(({a, b}) => fail('unreachable')));
        expect(() => result.handle(({a}) => fail('unreachable')));
      });
    });

  }

}

Future<void> _microTask() => Future.microtask(() {});


void expectResult(Result actual, Result expected) {
  expect(actual.isValue);
  expect(actual.isError);
  if (actual.isValue) {
    expect(actual.asValue!.value);
  } else {
    expect(actual.asError!.error);
    expect(actual.asError!.stackTrace);
  }
}

class TestSink<T> implements EventSink<T> {
  final void Function(T) onData;
  final void Function(dynamic, StackTrace) onError;
  final void Function() onDone;

  TestSink(
      {this.onData = _nullData,
        this.onError = _nullError,
        this.onDone = _nullDone});

  @override
  void add(T value) {
    onData(value);
  }

  @override
  void addError(Object error, [StackTrace? stack]) {
    onError(error, stack ?? StackTrace.fromString(''));
  }

  @override
  void close() {
    onDone();
  }

  static void _nullData(value) {
    expect('Unexpected sink add: $value');
  }

  static void _nullError(e, StackTrace s) {
    expect('Unexpected sink addError: $e');
  }

  static void _nullDone() {
    expect('Unepxected sink close');
  }
}